package com.atguigu.tingshu.user.service.impl;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.atguigu.tingshu.common.cache.ClearCache;
import com.atguigu.tingshu.common.cache.RedisCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.model.user.UserPaidAlbum;
import com.atguigu.tingshu.model.user.UserPaidTrack;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.user.strategy.ItemTypeStrategyFactory;
import com.atguigu.tingshu.vo.system.LoginVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    private final WxMaService wxMaService;
    private final RedisTemplate<Object, Object> redisTemplate;
    private final RabbitService rabbitService;
    private final UserPaidAlbumMapper userPaidAlbumMapper;
    private final UserPaidTrackMapper userPaidTrackMapper;
    private final ItemTypeStrategyFactory itemTypeStrategyFactory;

    public UserInfoServiceImpl(WxMaService wxMaService, RedisTemplate<Object, Object> redisTemplate, RabbitService rabbitService, UserPaidAlbumMapper userPaidAlbumMapper, UserPaidTrackMapper userPaidTrackMapper, ItemTypeStrategyFactory itemTypeStrategyFactory) {
        this.wxMaService = wxMaService;
        this.redisTemplate = redisTemplate;
        this.rabbitService = rabbitService;
        this.userPaidAlbumMapper = userPaidAlbumMapper;
        this.userPaidTrackMapper = userPaidTrackMapper;
        this.itemTypeStrategyFactory = itemTypeStrategyFactory;
    }


    @Override
    public Map<String, String> wxLogin(String code) {
        // 创建期望保存token的map集合
        Map<String, String> resultMap = new HashMap<>();

        // 根据入参提交临时票据，调用微信获取微信账户唯一标识接口，得到微信账户openId
        try {
            WxMaJscode2SessionResult sessionInfo = this.wxMaService.getUserService().getSessionInfo(code);
            if (sessionInfo != null) {
                String wxOpenId = sessionInfo.getOpenid();

                // 根据微信唯一标识查询用户记录，判断用户是否已经存在
                UserInfo userInfo = this.baseMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
                        .eq(UserInfo::getWxOpenId, wxOpenId));
                // 如果为首次登录用户，则保存用户记录
                if (userInfo == null) {
                    userInfo = new UserInfo();
                    userInfo.setWxOpenId(wxOpenId);
                    userInfo.setNickname("听友" + IdUtil.getSnowflakeNextId());
                    userInfo.setAvatarUrl(SystemConstant.ACCOUNT_DEFAULT_AVATAR_URL);
                    this.baseMapper.insert(userInfo);
                    // RabbitMq 发送消息，通知账户微服务新增账户记录
                    this.rabbitService.sendMessage(
                            MqConst.EXCHANGE_USER,
                            MqConst.ROUTING_USER_REGISTER,
                            userInfo.getId());

                }

                // 基于用户记录生成Token
                String token = this.createToken(userInfo);

                // 封装用户token
                resultMap.put("token", token);
            }
        } catch (WxErrorException e) {
            log.error("[用户服务]微信登录异常：错误信息：{}", e.getMessage(), e);
            throw new GuiguException(ResultCodeEnum.WX_LOGIN_ERROR);
        }
        return resultMap;
    }

    @Override
    @RedisCache(prefix = RedisConstant.USER_INFO_KEY_PREFIX)
    public UserInfoVo getUserInfoVoByUserId(Long userId) {
        UserInfo userInfo = this.baseMapper.selectById(userId);
        return BeanUtil.copyProperties(userInfo, UserInfoVo.class);
    }

    @Override
    @ClearCache(prefix = RedisConstant.USER_INFO_KEY_PREFIX, keyField = "userId")
    public void updateUser(Long userId, UserInfoVo userInfoVo, String token) {
        // 更新数据库用户信息
        UserInfo userInfo = this.baseMapper.selectById(userId);
        userInfo.setAvatarUrl(userInfoVo.getAvatarUrl());
        userInfo.setNickname(userInfoVo.getNickname());
        this.baseMapper.updateById(userInfo);
        // 更新缓存用户信息
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        BeanUtil.copyProperties(userInfo, userInfoVo);
        redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
    }

    @Override
    public Map<String, String> userLogin(LoginVo loginVo) {
        // 创建期望保存token的map集合
        Map<String, String> resultMap = new HashMap<>();
        // 手机号和密码校验
        String username = loginVo.getUsername();
        String password = loginVo.getPassword();
        if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR);
        }
        // 根据用户名查询系统用户，如果用户不存在，则抛出异常
        UserInfo userInfo = this.baseMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getPhone, username));
        if (userInfo == null) {
            throw new GuiguException(ResultCodeEnum.LOGIN_MOBLE_ERROR);
        }
        // 检查用户状态，如果用户被锁定，则抛出异常
        if (SystemConstant.USER_STATUS_LOCK.equals(userInfo.getStatus())) {
            throw new GuiguException(ResultCodeEnum.ACCOUNT_STOP);
        }
        // 比较用户输入的密码和系统用户密码，如果不匹配，则抛出异常
        if (!SecureUtil.md5(password).equals(userInfo.getPassword())) {
            throw new GuiguException(ResultCodeEnum.PASSWORD_ERROR);
        }
        // 基于用户记录生成Token
        String token = this.createToken(userInfo);

        // 封装用户token
        resultMap.put("token", token);
        return resultMap;
    }

    public String createToken(UserInfo userInfo) {
        String token = IdUtil.fastUUID();
        String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
        // 封装用户信息
        UserInfoVo userInfoVo = BeanUtil.copyProperties(userInfo, UserInfoVo.class);
        // 将用户令牌存入Redis。Key:前缀+token ，Value:用户信息
        this.redisTemplate.opsForValue().set(loginKey, userInfoVo, RedisConstant.USER_LOGIN_KEY_TIMEOUT, TimeUnit.SECONDS);
        return token;
    }

    @Override
    public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> trackIdList) {
        // 创建存放当前用户某一页中声音列表购买情况的map集合，初始值为 0，表示未购买
        Map<Long, Integer> checkResult = trackIdList.stream()
                .collect(Collectors.toMap(trackId -> trackId, trackId -> 0));
        // 判断用户是否购买过该专辑，查询用户已购专辑表（user_paid_album）
        Long count = this.userPaidAlbumMapper.selectCount(new LambdaQueryWrapper<UserPaidAlbum>()
                .eq(UserPaidAlbum::getUserId, userId)
                .eq(UserPaidAlbum::getAlbumId, albumId));
        if (count > 0) {
            // 用户已购买过该专辑，说明用户已购买过该专辑下的所有声音
            return checkResult.entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, entry -> 1));
        }


        // 如果用户没用购买过该专辑，根据用户id和声音id列表查询用户已购声音列表（user_paid_track）
        List<UserPaidTrack> userPaidTracks = this.userPaidTrackMapper.selectList(new LambdaQueryWrapper<UserPaidTrack>()
                .eq(UserPaidTrack::getUserId, userId)
                .in(UserPaidTrack::getTrackId, trackIdList));
        // 如果用户已购声音列表不为空，则循环遍历，将已购声音id设置为 1, 表示已购买
        if (CollectionUtil.isNotEmpty(userPaidTracks)) {
            userPaidTracks.forEach(userPaidTrack -> checkResult.put(userPaidTrack.getTrackId(), 1));
        }
        return checkResult;
    }

    @Override
    public Boolean isSubscribe(Long albumId, Long userId) {
        if (userId == null) {
            return false;
        }
        // TODO
        return false;
    }

    @Override
    public Boolean isPaidAlbum(Long albumId, Long userId) {
        Long count = this.userPaidAlbumMapper.selectCount(new LambdaQueryWrapper<UserPaidAlbum>()
                .eq(UserPaidAlbum::getUserId, userId)
                .eq(UserPaidAlbum::getAlbumId, albumId));
        return count > 0;
    }

    @Override
    public List<Long> findUserPaidTrackList(Long albumId, Long userId) {
        List<UserPaidTrack> userPaidTracks = this.userPaidTrackMapper.selectList(new LambdaQueryWrapper<UserPaidTrack>()
                .eq(UserPaidTrack::getUserId, userId)
                .eq(UserPaidTrack::getAlbumId, albumId));
        if (CollectionUtil.isNotEmpty(userPaidTracks)) {
            return userPaidTracks.stream()
                    .map(UserPaidTrack::getTrackId)
                    .toList();
        }
        return List.of();
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
        this.itemTypeStrategyFactory.getItemTypeStrategy(userPaidRecordVo.getItemType())
                .savePaidRecord(userPaidRecordVo);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public void updateVipExpireStatus() {
        // 1.查询vip会员已到期的用户
        List<UserInfo> userInfoList = this.list(new LambdaQueryWrapper<UserInfo>()
                .select(UserInfo::getId, UserInfo::getIsVip)
                .eq(UserInfo::getIsVip, 1)
                .le(UserInfo::getVipExpireTime, DateUtil.beginOfDay(new Date())));

        // 2.批量更新用户vip状态
        if (CollectionUtil.isNotEmpty(userInfoList)) {
            userInfoList.forEach(userInfo -> {
                userInfo.setIsVip(0);
            });
            this.updateBatchById(userInfoList);
        }
    }

}
